// scaup - Scala up!
// (c) 2008-2009, Normen Müller, normen.mueller@gmail.com  
// $Id: GM.scala 2482 2009-10-07 16:55:33Z nmueller $
package locutor.model.parsing.grgen

import java.io.File
import java.text.MessageFormat.{format}

import scaup.Views._

import locutor.model.G

/** 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
case class GMError(message: String) extends Exception(message)

/** This object provides a simple interface to the GM parser class.
 * 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
object GM extends GMParser {
  def apply(input: File): Any =
    if(input == null || !(input exists))
      throw new GMError(format("Physical GM file may not be null (''{0}'').", input))
    else apply(input.content)
  
  def apply(input: String) =
    if(input == null)
      throw new GMError("Physical GM representation may not be null.")
    else parse(input)
  
  def parse(input: String) = phrase(gm)(new lexical.Scanner(input)) match {
    case Success(result, _) => result
    case n@_                => throw new GMError(format("GM parsing error in ''{0} ...}''\n", input.lines.next) + n)
  }
}

import scala.util.parsing.combinator._
import scala.util.parsing.combinator.lexical._
import scala.util.parsing.input.CharArrayReader.EofCh

/** 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
private [grgen] class GMLexer extends StdLexical with RegexParsers {
  override type Elem = Char
  
  override def token: Parser[Token] = 
    ( string ^^ StringLit
    | number ^^ NumericLit
    | delim
    | id     ^^ {case i if reserved contains i => Keyword(i) case i => Identifier(i)}  
    | EofCh ^^^ EOF    
    | '\"' ~> failure("Unterminated string")
    | failure("Illegal character")
    )
  
  //override def whitespace: Parser[Any] = rep(whitespaceChar | '/' ~ '/' ~ rep(chrExcept(EofCh, '\n')))
  
  def string  = '\"' ~> rep(charSeq | chrExcept('\"', '\n', EofCh)) <~ '\"' ^^ { _ mkString "" }  
  
  def number  = """-?(\d+(\.\d*)?|\d*\.\d+)([eE][+-]?\d+)?[fFdD]?""".r
  
  def id      = rep(letter | digit | elem("underscore", _=='_')) ^^ { _ mkString "" }
  
  def charSeq = 
    ( '\\' ~ '\"' ^^^ "\"" | '\\' ~ '\\' ^^^ "\\" | '\\' ~ '/'  ^^^ "/" | '\\' ~ 'b'  ^^^ "\b" | '\\' ~ 'f'  ^^^ "\f" 
    | '\\' ~ 'n'  ^^^ "\n" |'\\' ~ 'r'  ^^^ "\r" |'\\' ~ 't'  ^^^ "\t" |'\\' ~ 'u' ~> unicodeBlock
    )
  
  import scala.io.UTF8Codec.{encode}
  def unicodeBlock = hexDigit ~ hexDigit ~ hexDigit ~ hexDigit ^^ {
    case a ~ b ~ c ~ d => new String(encode(Integer.parseInt(List(a, b, c, d) mkString "", 16)))
  }
  
  val hexDigits = Set[Char]() ++ "0123456789abcdefABCDEF".toArray
  
  def hexDigit = elem("hex digit", hexDigits.contains(_))
}

/** 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
private [grgen] class GMParser extends ExprParser {
  /* Fill in abstract defs*/
  type Tokens = GMLexer
  val lexical: Tokens = new GMLexer
  
  /* Configure lexical parsing */                                 
  lexical.reserved ++= List(
    "enum", "abstract", "const", "node", "edge", "class", "extends", "arbitrary", "directed", "undirected", "connect",
    "inherited", "set"
  ) ++ ExprParser.reserved
  
  lexical.delimiters ++= List("{", "}", "[", "]", "->", "--", "<->", ";", ",", "=") ++ ExprParser.delimiters
  
  /* Define the grammar */
  def gm = (enum_stmt*) ~ (class_stmt*) 
    
  def enum_stmt = "enum" ~ ident ~ "{" ~ enum_val ~ (("," ~ enum_val)*) ~ "}"
  def enum_val = ident ~ opt("=" ~ expr)
  def enum_type = ident ~ "::" ~ ident

  def class_stmt = opt("abstract") ~ opt("const") ~ (node_stmt | edge_stmt)

  def node_stmt = "node" ~ "class" ~ ident ~ opt(extends_stmt) ~ (";" | "{" ~ ((attr_stmt)*) ~ "}")
  
  def edge_stmt = opt(opt("arbitrary") ~ ("undirected" | "undirected")) ~ edge_class
  
  def edge_class = "edge" ~ "class" ~ ident ~ opt(extends_stmt) ~ opt(connect_stmt) ~ (";" | "{" ~ ((attr_stmt)*) ~ "}")
  
  def extends_stmt = "extends" ~ ((ident <~ opt(","))*)
  
  def connect_stmt = "connect" ~ (connect_inherited | connect_normal)
  def connect_inherited = "inherited" ~ (("," ~ connect_normal)*)
  def connect_normal = (node_contraint ~ ("->" | "<->" | "--") ~ node_contraint <~ opt(","))+
  def node_contraint = ident ~ opt("[" ~ ("*" | "+" | numeric ~ opt(":" ~ ("*" | numeric))) ~ "]")
  
  def attr_stmt = ident ~ (":" ~ attr_type ~ opt("=" ~ expr) ~ ";" | "=" ~ expr ~ opt(";"))
  def attr_type = ident | primitive_type | set_type
  def primitive_type = "boolean" | "int" | "float" | "double" | "string" | "object"
  def set_type  = "set" ~ "<" ~ primitive_type ~ ">"  
}
